package com.snts.synchronization.client;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.HashMap;

import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.DefaultHttpClient;

import com.snts.synchronization.SyncService;
import com.snts.synchronization.core.AuthRequest;
import com.snts.synchronization.core.AuthResponse;
import com.snts.synchronization.core.ErrorObject;
import com.snts.synchronization.core.ResponseStatus;
import com.snts.synchronization.core.SyncRequest;
import com.snts.synchronization.core.SyncResponse;
import com.snts.synchronization.core.UUID;
import com.snts.synchronization.garbage.Utils;
import com.snts.synchronization.protocol.ProtocolException;
import com.snts.synchronization.protocol.adapter.AuthRequestPA;
import com.snts.synchronization.protocol.adapter.AuthResponsePA;
import com.snts.synchronization.protocol.adapter.SyncRequestPA;
import com.snts.synchronization.protocol.adapter.SyncResponsePA;

public class SyncClient implements SyncService {

	private static final Log log = LogFactory.getLog(SyncClient.class);
	private URL url;

	public SyncClient(URL url) {
		super();
		this.url = url;
	}

	public String auth(String userName, String password) throws Exception {
		AuthRequest authRequest = new AuthRequest();
		authRequest.setUserName(userName);
		authRequest.setPassword(password);
		AuthResponse authResponse = auth(authRequest);
		if (authResponse.getStatus() == ResponseStatus.OK) {
			return authResponse.getSessionId();
		} else {
			throw new AuthException("Authentication failure for user " + userName);
		}
	}

	@Override
	public AuthResponse auth(AuthRequest authRequest) throws Exception {
		ByteArrayOutputStream baos = new ByteArrayOutputStream(4 * 1024);
		new AuthRequestPA().serializeMessage(authRequest, baos);
		byte[] bytes = baos.toByteArray();
		InputStream is = sendMessage("auth", bytes);
		if (is != null) {
			baos = new ByteArrayOutputStream(4 * 1024);
			IOUtils.copy(is, baos);
			bytes = baos.toByteArray();
			if (log.isDebugEnabled()) {
				log.debug("AuthResponse: ");
				for (String str : IOUtils.readLines(new ByteArrayInputStream(bytes))) {
					log.debug(str);
				}
			}
			return new AuthResponsePA().parseMessage(new ByteArrayInputStream(bytes));
		}
		return null;
	}

	@Override
	public SyncResponse sync(SyncRequest syncRequest) throws Exception {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		new SyncRequestPA().serializeMessage(syncRequest, baos);
		if(log.isDebugEnabled()){
			log.debug("Sending sync request");
			log.debug("message content");
			log.debug(new String(baos.toByteArray()));
		}
		InputStream is = sendMessage("sync", baos.toByteArray());
		try {
			if (is != null) {
				baos = new ByteArrayOutputStream();
				IOUtils.copy(is, baos);
				log.debug("Got sync response content:");
				log.debug(new String(baos.toByteArray()));
				SyncResponse sr = new SyncResponsePA().parseMessage(new ByteArrayInputStream(baos.toByteArray()));
				if(sr.getStatus() == ResponseStatus.FAIL){
					log.fatal("Error response(");
					log.fatal("errorCode: " + ErrorObject.errorCodeToString(sr.getErrorObject().getErrorCode()));
					log.fatal("errorMessage: " + sr.getErrorObject().getErrorMessage());
					log.fatal("additionalInformation: " + sr.getErrorObject().getAdditionalInformation() + ")");
					throw new ProtocolException(sr.getErrorObject());
				}
				return sr;
			} else {
				throw new Exception("");
			}
		} finally {
			is.close();
		}
	}

	@Override
	public boolean ping(String sessionId) throws Exception {
		String png = new String(Utils.getBytesFromInputStream(sendMessage("png.snc", "<png>" + sessionId + "</png>")));
		png = png.substring(png.indexOf("<png>"), png.indexOf("</png>"));
		return "VALID".equals(png);
	}

	private InputStream sendMessage(String handlerName, byte[] message) throws ClientProtocolException, IOException {
		HttpClient httpClient = new DefaultHttpClient();
		String strUrl = url.toString();
		strUrl += (strUrl.endsWith("/") ? "" : "/") + handlerName + ".snc";
		log.debug("Sending request to url - \"" + strUrl + "\"");
		HttpPost httpPost = new HttpPost(strUrl);
		ByteArrayEntity entity = new ByteArrayEntity(message);
		httpPost.setEntity(entity);
		HttpResponse response = httpClient.execute(httpPost);
		HttpEntity httpEntity = response.getEntity();
		if (httpEntity != null) {
			return httpEntity.getContent();
		} else {
			return null;
		}
	}

	private InputStream sendMessage(String handlerName, String message) throws ClientProtocolException, IOException {
		return sendMessage(handlerName, message.getBytes());
	}

	public static void main(String[] args) throws Exception {
		AuthRequest authRequest = new AuthRequest();
		authRequest.setUserName("e5009e8a-c1b5-11e1-acf8-37dfad25aa88");
		authRequest.setPassword("123");
		SyncClient syncClient = new SyncClient(new URL("http://localhost:8080/Synchronizator/"));
		AuthResponse authResponse = syncClient.auth(authRequest);
		if (ResponseStatus.OK == authResponse.getStatus()) {
			System.out.println("Response status: " + ResponseStatus.OK.name());
			System.out.println(authResponse.getSessionId());
		}
		System.out.println(syncClient.ping(authResponse.getSessionId()));
	}

	@Override
	public void confirm(SyncResponse syncResponse) throws Exception {
		throw new UnsupportedOperationException("client side confirm not supported!");
	}

	public static class AuthException extends Exception {

		private ErrorObject errorObject;

		public ErrorObject getErrorObject() {
			return errorObject;
		}

		public void setErrorObject(ErrorObject errorObject) {
			this.errorObject = errorObject;
		}

		/**
		 * 
		 */
		public AuthException() {
			super();
			// TODO Auto-generated constructor stub
		}

		/**
		 * @param message
		 * @param cause
		 * @param enableSuppression
		 * @param writableStackTrace
		 */
		public AuthException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
			super(message, cause, enableSuppression, writableStackTrace);
			// TODO Auto-generated constructor stub
		}

		/**
		 * @param message
		 * @param cause
		 */
		public AuthException(String message, Throwable cause) {
			super(message, cause);
			// TODO Auto-generated constructor stub
		}

		/**
		 * @param message
		 */
		public AuthException(String message) {
			super(message);
			// TODO Auto-generated constructor stub
		}

		/**
		 * @param cause
		 */
		public AuthException(Throwable cause) {
			super(cause);
			// TODO Auto-generated constructor stub
		}

	}

}
